package application.view.gui;

import application.controllers.MainViewController;
import application.controllers.PeriodController;
import application.controllers.PeriodException;
import application.models.Observable;
import application.models.Observer;
import application.models.collections.CarCollection;
import application.models.datalayer.Date;
import application.models.exceptions.InvalidAttributeException;
import application.models.models.CarModel;
import application.models.models.ReservationModel;
import application.view.gui.GraphicalUserInterface;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JTable;
import javax.swing.JScrollPane;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.swing.text.AttributeSet;

import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Container;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

/**
 * This view is responsible for rendering the main view window given overview over reservations 
 * 
 * @author Philip Rasmussen & Morten Therkildsen
 */
public class MainView {

	private static GraphicalUserInterface gui;
	private static boolean applicationHasStarted = false;
	private static MainView instance;

	private final static String TITLE = "Reservation Overview";
	private final static Dimension PREFFERED_SIZE = new Dimension(800, 500);
	private final static Dimension MINIMUM_SIZE = PREFFERED_SIZE;
	
	private final static String dateFieldRestrictorPattern = "^([0-9]|/){0,10}$";

	private MainViewController mainController;
	private PeriodController periodController;
	private Set<ReservationModel> tempSet;

	private JTable table;
	private JButton editButton;
	private JButton deleteButton;

	private JFrame window;

	public static void startup(GraphicalUserInterface applicationGui){

		if(applicationHasStarted) return;

		gui = applicationGui;

		JMenuItem showMainViewMenuItem = new JMenuItem(TITLE);
		showMainViewMenuItem.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent event){

				MainView.open();
			}
		});

		try{
			gui.getSharedMenuBuilder().addMenuItem("Window", showMainViewMenuItem);
		} catch(DuplicateMenuItemException e){/*IGNORE*/}

		applicationHasStarted = true;
	}

	public static void open(){

		if(instance == null) instance = new MainView();

		instance.focus();
	}

	private MainView(){

		try{
			periodController = new PeriodController(7);
		} catch(PeriodException e){throw new RuntimeException("Unexpected logic error", e);}
		mainController = new MainViewController();

		window = new JFrame();

		setWindowProperties();

		window.setJMenuBar(createMenu());
		window.add(createPeriodSelectionPane(), "North");
		window.add(createContent(), "Center");
		window.add(createButtonsPane(), "South");

		this.focus();
	}

	public void focus(){

		window.setVisible(true);
		window.toFront();
	}

	private void setWindowProperties(){

		window.setTitle(TITLE);
		window.setMinimumSize(MINIMUM_SIZE);
		window.setPreferredSize(PREFFERED_SIZE);

		window.addWindowListener(new WindowAdapter(){

			public void windowClosed(WindowEvent e) {

				instance = null;
			}
		});
	}

	private JMenuBar createMenu(){

		return gui.getSharedMenuBuilder().getMenuBar();
	}

	private JPanel createPeriodSelectionPane(){

		JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));

		panel.add(new JLabel("From: "));

		JTextField fromField = new JTextField();
		fromField.setDocument(new ValidatingDocument(dateFieldRestrictorPattern));

		try {fromField.getDocument().insertString(0, periodController.getFromDateAsString(), null);}
		catch(BadLocationException e){throw new RuntimeException("Unexpected logic failure.", e);}

		fromField.addFocusListener(new FocusListener(){

			public void focusGained(FocusEvent event){}

			public void focusLost(FocusEvent event){

				JTextField field = (JTextField) event.getComponent();
				Document document = field.getDocument();
				try {

					periodController.setFromDate(document.getText(0, document.getLength()));
					updateTable();
					return;

				} catch(PeriodException e){

					String message =	"The specified date was invalid. Please check that:\n\n" +
					"- The date follows the format DD/MM/YYYY\n" +
					"- \"From\" is a date coming before \"To\"\n\n" +
					"Please try again.";  		

					JOptionPane.showMessageDialog(window, message, "Invalid \"From\" date", JOptionPane.ERROR_MESSAGE);

					try{

						document.remove(0, document.getLength());
						document.insertString(0, periodController.getFromDateAsString(), null);
						field.grabFocus();

					} catch(BadLocationException e2){}

				} catch(BadLocationException e){}
			}
		});
		panel.add(fromField);

		panel.add(new JLabel("To: "));

		JTextField toField = new JTextField();
		toField.setDocument(new ValidatingDocument(dateFieldRestrictorPattern));

		try {toField.getDocument().insertString(0, periodController.getToDateAsString(), null);}
		catch(BadLocationException e){throw new RuntimeException("Unexpected logic failure.", e);}

		toField.addFocusListener(new FocusListener(){

			public void focusGained(FocusEvent event){}

			public void focusLost(FocusEvent event){

				JTextField field = (JTextField) event.getComponent();
				Document document = field.getDocument();
				try {

					periodController.setToDate(document.getText(0, document.getLength()));
					updateTable();
					
					return;
				} catch(PeriodException e){

					String message =	"The specified date was invalid. Please check that:\n\n" +
					"- The date follows the format DD/MM/YYYY\n" +
					"- \"From\" is a date coming before \"To\"\n\n" +
					"Please try again.";   		

					JOptionPane.showMessageDialog(window, message, "Invalid \"To\" date", JOptionPane.ERROR_MESSAGE);

					try{

						document.remove(0, document.getLength());
						document.insertString(0, periodController.getToDateAsString(), null);
						field.grabFocus();

					} catch(BadLocationException e2){}

				} catch(BadLocationException e){}
			}
		});
		panel.add(toField);

		return panel;
	}

	private JPanel createButtonsPane(){

		JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));

		deleteButton = new JButton("Delete");
		deleteButton.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent event){
				
				confirm();
			}
		});

		deleteButton.setEnabled(false);
		panel.add(deleteButton);

		editButton = new JButton("Edit");
		editButton.addActionListener(new ActionListener(){
			
			public void actionPerformed(ActionEvent e){
				
				edit();
			}
		});
		
		editButton.setEnabled(false);
		panel.add(editButton);

		JButton newButton = new JButton("New Reservation");
		newButton.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent event){

				ReservationView.open();
			}
		});
		panel.add(newButton);

		return panel;
	}

	private void confirm(){

		ReservationModel selected = mainController.getSelection();
		
		if(selected != null){
			
			int choice = JOptionPane.showConfirmDialog(window, "Are you sure you want to delete customer reservation: " + selected.getId() + "?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

			if(choice == JOptionPane.YES_OPTION){
				selected.delete();

			}
		} else{ // This should not happen in the finished program
			JOptionPane.showMessageDialog(window, "No reservation selected.", "Operation failed" , JOptionPane.OK_OPTION);
		
		}
	}

	private void edit(){
		
		ReservationModel selected = mainController.getSelection();
		
		if(selected != null){
			ReservationView.open(selected.getId());
			
		} else{ // This should not happen in the finished program
			JOptionPane.showMessageDialog(window, "No reservation selected.", "Operation failed" , JOptionPane.OK_OPTION);
		
		}
	}
	
	private Container createContent(){

		JScrollPane container = new JScrollPane();

		// Create table 1 - number plate + car type
		DefaultTableModel model = new DefaultTableModel();
		model.addColumn("Number Plate");
		model.addColumn("Car Type");
		model.addColumn("Reservations");

		table = new JTable(model) {

			private static final long serialVersionUID = -6846471394909693566L;

			// Disable cell edits
			public boolean isCellEditable(int rowIndex, int vColIndex) {

				return false;
			}
		};
		table.setColumnSelectionAllowed(false);
		table.setDragEnabled(false);
		table.setShowGrid(true);
		table.getTableHeader().setReorderingAllowed(false);
		table.getTableHeader().setResizingAllowed(false);
		table.setFocusable(false);
		table.setCellSelectionEnabled(false);
		table.setGridColor(Color.BLACK);

		// Column specific settings
		table.getColumnModel().getColumn(0).setMaxWidth(100);
		table.getColumnModel().getColumn(0).setPreferredWidth(100);

		table.getColumnModel().getColumn(1).setMaxWidth(100);
		table.getColumnModel().getColumn(1).setPreferredWidth(100);

		table.getColumnModel().getColumn(2).setCellRenderer(new DefaultTableCellRenderer(){

			private static final long serialVersionUID = 3344025844253327373L;

			public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column){

				if(!(value instanceof ReservationModel[])) return null;
				Set<ReservationModel> set = new HashSet<ReservationModel>();

				for(ReservationModel model : (ReservationModel[]) value){

					set.add(model);
				}

				tempSet = set;

				JComponent canvas = new JComponent(){

					private static final long serialVersionUID = 1L;

					public void paintComponent(Graphics g){

						mainController.drawReservationGraph(tempSet, mainController.getSelection(), periodController.getFromDate(), periodController.getToDate(), g);
					}
				};

				return canvas;
			}
		});

		table.addMouseListener(new MouseAdapter(){
			
			public void mouseClicked(MouseEvent event){
				
				int row = table.rowAtPoint(event.getPoint());
				int col = table.columnAtPoint(event.getPoint());
				
				if(col == 2){
					
					Rectangle rect = table.getCellRect(row, col, true);
					int x = event.getPoint().x - (int) rect.getX();
					int y = event.getPoint().y - (int) rect.getY();
					int height = (int) rect.getHeight();
					int width = (int) rect.getWidth();

					ReservationModel[] value = (ReservationModel[]) table.getValueAt(row, col);
					
					mainController.setSelection(mainController.getClickedReservation(value, x, y, height, width, periodController.getFromDate(), periodController.getToDate()));
					
					table.repaint();
					
					if(mainController.getSelection() == null){
						
						editButton.setEnabled(false);
						deleteButton.setEnabled(false);
					} else {
						
						editButton.setEnabled(true);
						deleteButton.setEnabled(true);
					}
				}
			}
		});
		
		// Populate tables with data
		CarCollection cars = new CarCollection();
		try{cars.load();}
		catch(IOException e){throw new RuntimeException("Could not load data.", e);}

		Date from = periodController.getFromDate();
		Date to = periodController.getToDate();

		for(CarModel car : cars){

			try{
				model.addRow(new Object[]{(String) car.getAttribute("number_plate"), (String) car.getAttribute("car_type"), car.getReservationsInPeriod(from, to)});
			}
			catch(InvalidAttributeException e){/* Ignore this failing model */}
		}

		
		// Reservation updates reloads the table
		ReservationModel.addGlobalObserver(new Observer(){
			
			public void notify(Observable subject){
				
				updateTable();
			}
		});
		
		
		container.setViewportView(table);

		return container;
	}
	
	public void updateTable(){
		
		DefaultTableModel model = (DefaultTableModel) table.getModel();
		
		CarCollection cars = new CarCollection();
		try{cars.load();}
		catch(IOException e){throw new RuntimeException("Could not load data.", e);}

		Date from = periodController.getFromDate();
		Date to = periodController.getToDate();

		for(CarModel car : cars){

			try{
				model.removeRow(0);
				model.addRow(new Object[]{(String) car.getAttribute("number_plate"), (String) car.getAttribute("car_type"), car.getReservationsInPeriod(from, to)});
			}
			catch(InvalidAttributeException e){/* Ignore this failing model */}
		}
		
		table.repaint();
	}
	
	private class ValidatingDocument extends PlainDocument {

		private String pattern;
		
		public ValidatingDocument(String pattern){
			
			this.pattern = pattern;
		}
		
		private static final long serialVersionUID = -200088186613511523L;

		public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException {

			if(string == null) return;

			String all = getText(0, getLength());
			String before = all.substring(0, offset);
			String after = all.substring(offset, all.length());

			// Validate
			if(!(before+string+after).matches(pattern)) return;

			super.insertString(offset, string, attributes);
		}

	}
}
